package boardData;

import iodevice.CommandPhalanx;

import java.io.BufferedWriter;
import java.io.IOException;

//TODO Klassen: RepresentationMain, TimeManagement
//TODO KDOParser: Senden des Boards an RepresentationMain.
//TODO Something other...
//TODO RepresentationMain-> Klasse die die Daten über das aktuelle spiel vorhält.
//TODO Zugriff auf das Board währe dann über RepresentationMain.getBoard() (theoretisch)

/**
 * Klasse die ein Brett darstellt
 * Das Brett wird als 10x12 Array dargestellt, mit Position 21 als a8 und Position 98 als h1.
 * 
 *      0: 1: 2: 3: 4: 5: 6: 7: 8: 9:
 *  00: xx|xx|xx|xx|xx|xx|xx|xx|xx|xx
 *  10: xx|xx|xx|xx|xx|xx|xx|xx|xx|xx
 *  20: xx|a8|b8|c8|d8|e8|f8|g8|h8|xx
 *  30: xx|a7|b7|c7|d7|e7|f7|g7|h7|xx
 *  40: xx|a6|b6|c6|d6|e6|f6|g6|h6|xx
 *  50: xx|a5|b5|c5|d5|e5|f5|g5|h5|xx
 *  60: xx|a4|b4|c4|d4|e4|f4|g4|h4|xx
 *  70: xx|a3|b3|c3|d3|e3|f3|g3|h3|xx
 *  80: xx|a2|b2|c2|d2|e2|f2|g2|h2|xx
 *  90: xx|a1|b1|c1|d1|e1|f1|g1|h1|xx
 * 100: xx|xx|xx|xx|xx|xx|xx|xx|xx|xx
 * 110: xx|xx|xx|xx|xx|xx|xx|xx|xx|xx
 * 
 * @author Fabian Hewer
 * @version V0.a.7 (4.8.'11)
 *
 */
public class Board implements Cloneable {
	//Variablen
	private BoardElement[] board = new BoardElement[120]; //Array für experimentelle implementierung.
	private int enPassant;		//Feld auf dem EnPassant möglich ist.
//	private String castling;	//"QKqk" Rochademöglichkeiten.
//	private String sideToMove;  //Farbe die am Zug ist.
	private String currentMove; //Zug, der die Stellung hervorgebracht hat.
	private History history = new History();
	//Booleans
	private boolean isSideWhite = true;
	private boolean casWhiteKingPos = false; //Rochade
	private boolean casWhiteQueenPos = false;//   %
	private boolean casBlackKingPos = false; //   %   
	private boolean casBlackQueenPos = false;//   %   
	
	//Konstanten
	public static final int ILLEGAL_FIELD = 999;
	public static final int EMPTY_FIELD = 0;	 //Immer Null...
	public static final int WHITE_KING = 3;		 //Weiß immer positiv
	public static final int BLACK_KING = -3;	 //Schwarz immer weiß
	public static final int WHITE_QUEEN = 7;
	public static final int BLACK_QUEEN = -7;
	public static final int WHITE_PAWN = 1;
	public static final int BLACK_PAWN = -1;
	public static final int WHITE_BISHOP = 5;
	public static final int BLACK_BISHOP = -5;
	public static final int WHITE_KNIGHT = 2;
	public static final int BLACK_KNIGHT = -2;
	public static final int WHITE_ROOK = 6;
	public static final int BLACK_ROOK = -6;
	
	//--// Konstruktoren
	public Board(String[] positionString) {
		CommandPhalanx.info("Parse FENString!"); //TODO Test
		String fen = positionString[0].substring(0);
		String moves;
		if(positionString[1] == null)
			moves = "";
		else
			moves = positionString[1].substring(0);
		// Initalisieren des Feldes aus dem Fenstring.
		for(int field = 0; field < 120; field++) {
			if(field <= 19 || field >= 100 || field%10 == 9 || field%10 == 0) {
				board[field] = new BoardElement();
			} else {
				char pieceOnField = fen.charAt(0);
				//Experimentelle Implementierung
				switch(pieceOnField){
					case 'p': board[field] = new BoardElement(BLACK_PAWN); break;
					case 'r': board[field] = new BoardElement(BLACK_ROOK); break;
					case 'n': board[field] = new BoardElement(BLACK_KNIGHT); break;
					case 'b': board[field] = new BoardElement(BLACK_BISHOP); break;
					case 'k': board[field] = new BoardElement(BLACK_KING); break;
					case 'q': board[field] = new BoardElement(BLACK_QUEEN); break;
					case 'P': board[field] = new BoardElement(WHITE_PAWN); break;
					case 'R': board[field] = new BoardElement(WHITE_ROOK); break;
					case 'N': board[field] = new BoardElement(WHITE_KNIGHT); break;
					case 'B': board[field] = new BoardElement(WHITE_BISHOP); break;
					case 'K': board[field] = new BoardElement(WHITE_KING); break;
					case 'Q': board[field] = new BoardElement(WHITE_QUEEN); break;
					case '8': board[field] = new BoardElement(EMPTY_FIELD); field++;
					case '7': board[field] = new BoardElement(EMPTY_FIELD); field++;
					case '6': board[field] = new BoardElement(EMPTY_FIELD); field++;
					case '5': board[field] = new BoardElement(EMPTY_FIELD); field++;
					case '4': board[field] = new BoardElement(EMPTY_FIELD); field++;
					case '3': board[field] = new BoardElement(EMPTY_FIELD); field++;
					case '2': board[field] = new BoardElement(EMPTY_FIELD); field++;
					case '1': board[field] = new BoardElement(EMPTY_FIELD); break;
					case '/': field--; break;
				}
			CommandPhalanx.info(fen); //TODO Test
			fen = fen.substring(1).trim();
			}
		}
		int tokenEnd;
		String token;
		for(int tokenNr = 0; tokenNr <= 4; tokenNr++){
			CommandPhalanx.info("TokenNr " + tokenNr + ": " + fen); //TODO Test
			tokenEnd = fen.trim().indexOf(" ");
			CommandPhalanx.info("" + tokenEnd); //TODO Test
			if(tokenEnd < 0)
				token = fen.substring(0).trim();
			else
				token = fen.substring(0,tokenEnd).trim();
			switch (tokenNr){
			case 0: if(token.equals("w"))  //Token besagt welche Seite in der momentanen Stellung am Zug ist.
						isSideWhite = true;
					else
						isSideWhite = false;
					break;
			case 1: if(token.equals("-") || token.equals("o")) { //Token besagt auf welchem Feld enPassant möglich ist.
						enPassant = 0;
					} else {
						enPassant = toField(token);
					} break;
			case 2: if(token.indexOf("K") >= 0) //Token besagt welche Rochaden möglich sind.
						casWhiteKingPos = true;	//Werte sind -1 wenn die entsprechende Rochade nicht möglich ist.
					if(token.indexOf("Q") >= 0)	//dies trifft immer zu, da nach dem Token für Rochade nurnoch integertokens kommen. 
						casWhiteQueenPos = true;
					if(token.indexOf("k") >= 0)
						casBlackKingPos = true;
					if(token.indexOf("q") >= 0)
						casBlackQueenPos =true;
					break;
			case 3: if(token.equals("o")) {	//Token besagt wieviele Halbzüge nach 50-Zügeregel gezogen wurden.
				} else {
					CommandPhalanx.info(token); //TODO Test
					history.setHistoryCount(Integer.parseInt(token));
				} break;
			case 4: break; //TODO FullmoveCounter notwendigkeit o.O?
			}
			if(tokenEnd != -1)
				fen = fen.substring(tokenEnd).trim();
		}
		CommandPhalanx.info("FENString geparst!"); //TODO Test
		history.toHistory((Board) clone());
		System.out.println(toString()); //TODO TEST
		CommandPhalanx.info("Parse Moves"); //TODO Test
		int moveEnd;
		String currentMove;
		while(moves != null && !moves.isEmpty()) {
			moveEnd = moves.indexOf(' ');
			if(moveEnd < 0)
				currentMove = moves.substring(0).trim();
			else
				currentMove = moves.substring(0,moveEnd).trim();
			if((currentMove.length() == 5)) {//Bauernumwandlung
				int start = toField(currentMove.substring(0,2)); //Zuganfangsposition
				int destination = toField(currentMove.substring(2,4)); //Zugendposition
				char piece = currentMove.charAt(4);
				move(start, destination, piece);
			} else {
				int start = toField(currentMove.substring(0,2)); //Zuganfangsposition
				int destination = toField(currentMove.substring(2,4)); //Zugendposition
				move(start, destination);
			}
			if(moveEnd >= 0)
				moves = moves.substring(moveEnd).trim();
			else
				moves = null;

		CommandPhalanx.info("Moves geparst"); //TODO Test
		}
//	System.out.println(toString()); //TODO TEST!!
	}	
	
	public Board(Board board) {
		for(int field = 0; field < 120; field++) {
			this.board[field] = new BoardElement(board.board[field].getPiece());
		}
		this.enPassant = board.enPassant;
		this.currentMove = board.currentMove;
		this.history = new History(history);
		this.isSideWhite = board.isSideWhite;
		this.casWhiteKingPos = board.casWhiteKingPos;
		this.casWhiteQueenPos = board.casWhiteQueenPos;
		this.casBlackKingPos = board.casBlackKingPos;
		this.casBlackQueenPos = board.casBlackQueenPos;
	}
	//--//
	
	public static void main(String[] Args) { //TODO TEST!!
		String[] position = new String[2];
		position[0] = "rnbkqbnr/ppppp1pp/8/5p2/8/Q7/NP1B2p1/R3K3 w f5 Qqk 0 0";
		position[1] = "b2b4 e7e5";
		Board board = new Board(position);
		board.move(31, 51);
		board.move(95, 93);
		board.move(87, 97, BLACK_QUEEN);
		System.out.println(board.toAlgebraic(11));
		System.out.println(board.toAlgebraic(91));
		System.out.println(board.toAlgebraic(98));
		System.out.println(board.toAlgebraic(55));
		System.out.println(board.toField("e2"));
		System.out.println(board.toField("f6"));
		System.out.println(board.toField("h4"));
		System.out.println(board.toField("k7"));
		System.out.println(board.toField("illumina"));
	}
	
	/**
	 * Methode um ein Feld des Array Algebraisch (a1 ode so) darzustellen.
	 * @param fieldNr Nummer des Arrayfeldes
	 * @return Algebraische Schreibweise des Feldes.
	 */
	private String toAlgebraic(int fieldNr) {
		if(fieldNr <= 19 || fieldNr >= 100 || fieldNr%10 == 9 || fieldNr%10 == 0)
			return "n/v";
		else {
			String algebraic = "n/v";
			String algebraicRow = "/v";
			switch(fieldNr/10) {
			case 2: algebraicRow = ("8"); break;
			case 3: algebraicRow = ("7"); break;
			case 4: algebraicRow = ("6"); break;
			case 5: algebraicRow = ("5"); break;
			case 6: algebraicRow = ("4"); break;
			case 7: algebraicRow = ("3"); break;
			case 8: algebraicRow = ("2"); break;
			case 9: algebraicRow = ("1"); break;
			default: return algebraic;
			}
			switch(fieldNr%10) {
			case 1: algebraic = ("a" + algebraicRow); break;
			case 2: algebraic = ("b" + algebraicRow); break;
			case 3: algebraic = ("c" + algebraicRow); break;
			case 4: algebraic = ("d" + algebraicRow); break;
			case 5: algebraic = ("e" + algebraicRow); break;
			case 6: algebraic = ("f" + algebraicRow); break;
			case 7: algebraic = ("g" + algebraicRow); break;
			case 8: algebraic = ("h" + algebraicRow); break;
			default: return algebraic;
			}
			return algebraic;
		}
	}
	
	/**
	 * Methode um ein Feld in Algebraischer notation in die Arraystruktur umzubauen.
	 * @param algebraic Feld in Algebrascher notation
	 * @return Feld als Nummer.
	 */
	public int toField(String algebraic) {
		if(algebraic.length() != 2)
			return 0;
		else {
			int fieldNr = 0;
			char row = algebraic.charAt(0);
			char collumn = algebraic.charAt(1);
			switch(row) {
			case 'a': fieldNr+=1; break;
			case 'b': fieldNr+=2; break;
			case 'c': fieldNr+=3; break;
			case 'd': fieldNr+=4; break;
			case 'e': fieldNr+=5; break;
			case 'f': fieldNr+=6; break;
			case 'g': fieldNr+=7; break;
			case 'h': fieldNr+=8; break;
			default: break;
			}
			switch(collumn) {
			case '1': fieldNr+=90; break;
			case '2': fieldNr+=80; break;
			case '3': fieldNr+=70; break;
			case '4': fieldNr+=60; break;
			case '5': fieldNr+=50; break;
			case '6': fieldNr+=40; break;
			case '7': fieldNr+=30; break;
			case '8': fieldNr+=20; break;
			default: break;
			}
			return fieldNr;
		}
	}
	
	/**
	 * Experimentelle Methode, die einen Zug auf dem Brett ausführt
	 * @param start Feld auf dem die Figur steht die ziehen soll
	 * @param destination Feld auf das die Figur ziehen soll
	 */
	public void move(int start, int destination) {
		move(start, destination, EMPTY_FIELD);
	}
	

	/**
	 * Experimentelle Methode, die einen Zug auf dem Brett ausführt
	 * @param start Feld auf dem die Figur steht die ziehen soll
	 * @param destination Feld auf das die Figur ziehen soll
	 */
	public void move(int start, int destination, int piece) {
		int movedPiece = board[start].getPiece();
		int capturedPiece = board[destination].getPiece();
		int newEnPassant = 0;
//		CommandPhalanx.info("Moved Piece: " + movedPiece);
		if(movedPiece == BLACK_KING && start == 25) { //Schwarz: Zug mit König von Startfeld
			if(destination == 23) { //Rochade lang
				board[21].notOccupied();
				board[24].setPiece(BLACK_ROOK);
			} else if(destination == 27) { //Rochade kurz
				board[28].notOccupied();
				board[26].setPiece(BLACK_ROOK);
			}
			casBlackQueenPos = false;
			casBlackKingPos = false;
			System.out.println(toString());
		} else if(movedPiece == WHITE_KING  && start == 95) { //Weiß: Zug mit König von Startfeld
			if(destination == 93) { //Rochade lang
				board[91].notOccupied();
				board[94].setPiece(WHITE_ROOK);
			} else if(destination == 98) { //Rochade kurz
				board[98].notOccupied();
				board[96].setPiece(WHITE_ROOK);
			}
			casWhiteKingPos = false;
			casWhiteQueenPos = false;
		} else if(movedPiece == BLACK_PAWN){//Schwarz: Zug mit Bauer
			if (start/10 == 3 && destination/10 == 5)   //2-Felder Bauernzug
				newEnPassant = destination;
			else if(destination-10 == enPassant)		//EnPassantschlag
				board[destination-10].notOccupied();
			else if(destination/10 == 9)				//Umwandlung
				board[start].setPiece(piece);
			history.resetHistory();						//Historyreset 50-Zügeregel
		} else if(movedPiece == WHITE_PAWN){//Weiß: Zug mit Bauer
			if (start/10 == 8 && destination/10 == 6)   //2-Felder Bauernzug
				newEnPassant = destination;
			else if(destination+10 == enPassant)		//EnPassantschlag
				board[destination+10].notOccupied();
			else if(destination/10 == 2)				//Umwandlung
				board[start].setPiece(piece);
			history.resetHistory();						//Historyreset 50-Zügeregel
		} else if(movedPiece == BLACK_ROOK){//Schwarz: Zug mit Turm
			if(start == 21) //von Startfeld Queenside
				casBlackQueenPos = false;
			if(start == 28) //von Startfeld Kingside
				casBlackKingPos = false;
		} else if(movedPiece == WHITE_ROOK){//Weiß: Zug mit Turm
			if(start == 91) //von Startfeld Queenside
				casWhiteQueenPos = false;
			if(start == 98) //von Startfeld Kingside
				casWhiteKingPos = false;
		}
		//Ab hier wird die Methode immer durchgeführt.
		if(capturedPiece == BLACK_ROOK){//Schwarzer Turm geschlagen
			if(destination == 21) //auf Startfeld Queenside
				casBlackQueenPos = false;
			if(destination == 28) //auf Startfeld Kingside
				casBlackKingPos = false;		
		} else if(capturedPiece == WHITE_ROOK){//Weißer Turm geschlagen
			if(destination == 91) //auf Startfeld Queenside
				casBlackQueenPos = false;
			if(destination == 98) //auf Startfeld Kingside
				casBlackKingPos = false;		
		}
		board[destination].setPiece(board[start].getPiece()); 	//Das ist der Zug
		board[start].notOccupied();								// 		%
		enPassant = newEnPassant;
		isSideWhite = !isSideWhite;
		currentMove = (toAlgebraic(start) + toAlgebraic(destination));
		if(capturedPiece != EMPTY_FIELD)
			history.resetHistory();									//Historyreset 50-Zügeregel
		history.toHistory((Board) clone());
//		System.out.println(toString()); //TODO Test
	}
	
	/**
	 * Methode die das Brett Kopiert und auf dem kopierten Brett einen Zug ausführt und
	 * dieses dann zurückgibt. Angepasst für einen Bauer der das letzte feld erreicht.
	 * @param start Feld auf dem die Figur steht die ziehen soll
	 * @param destination Feld auf das die Figur ziehen soll
	 * @param piece Figur in die der Bauer gewandelt werden soll
	 * @return das neue Brett
	 */
	public Board moveClone(int start, int destination, int piece) {
		Board clone = (Board) clone();
		clone.move(start, destination, piece);
		return clone;
	}
	
	/**
	 * Methode die das Brett Kopiert und auf dem kopierten brett einen Zug ausführt und
	 * dieses dann zurückgibt.
	 * @param start Feld auf dem die Figur steht die ziehen soll
	 * @param destination Feld auf das die Figur ziehen soll
	 * @return das neue Brett
	 */
	public Board moveClone(int start, int destination) {
		Board clone = moveClone(start, destination, EMPTY_FIELD);
		return clone;
	}
	
	protected Object clone(){
		Board clone = new Board(this);
		return clone;
	}
	
	/**
	 * Methode die eine Kopie des Brettes aufgibt.
	 * @return
	 */
	public Board getClone(){
		return (Board) clone();
	}
	
	/**
	 * Methode die angibt, ob ein Feld nicht Gültig iss, leer ist oder welche Figur draufsteht.
	 * @param feld, dass überprüft werden soll.
	 * @return nicht Gültig, leer oder die Figur.
	 */	
	public int getPiece(int field) {
		return board[field].getPiece();
	}
	
	/**
	 * Methode die überprüft, ob eine Stellung auf einem Brett gleich der auf diesem Brett ist
	 * @param testBoard Brett das überprüft werden soll
	 * @return true wenn sowohl Stellung der Figuren, enPassant felder und Rochademöglichkeiten gleich sind.
	 */
	public boolean matches(Board testBoard) {
		for(int field = 0; field < 120; field++) {
			if(!(board[field].matches(testBoard.board[field])))
				return false;
		}
		if(!casWhiteKingPos == testBoard.casWhiteKingPos)
			return false;
		if(!casWhiteQueenPos == testBoard.casWhiteQueenPos)
			return false;
		if(!casBlackKingPos == testBoard.casBlackKingPos)
			return false;
		if(!casBlackQueenPos == testBoard.casBlackQueenPos)
			return false;
		if(!(enPassant != testBoard.enPassant))
			return false;
		return true;
	}
	/**
	 * Methode die die Farbe eines Feldes zurückgibt
	 * @param field Nummer des Feldes
	 * @return true wenn Weiß, false wenn schwarz, undefined wenn Platin.
	 */
	public boolean giveColour(int field) {
		return(board[field].isWhite());
		//if(board[field].isWhite() == "Platin")
		//return undefined.
	}
	
	/**
	 * Gibt die Länge des Arrays des Brettes zurück
	 * @return länge des Brettfeldes
	 */
	public int getArrayLength() {
		return board.length;
	}
	
	/**
	 * Methode die ein Standartbrett in Grundaufstellung zurückgibt(zu Testzwecken implementiert)
	 * @return das Brett.
	 */
	public static Board giveDefaultBoard() {
		String[] position = new String[2];
		position[0] = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - QKqk 0 0";
		position[1] = "";
		Board board = new Board(position);
		return board;
	}
	
//	/**
//	 * Methode die die Rochademöglichkeiten zurückgibt.
//	 * @return String dder Rochademöglichkeiten ("QKqk")
//	 */
//	public String getCastlingPosibilities() {
//		return castling;
//	}
	
	/**
	 * Methode die zurückgibt, ob die kleine Rochade möglich ist-
	 * @return true wenn true und fals wenn nich
	 */
	public boolean getCasKingside() {
		if (isSideWhite)
		return casWhiteKingPos;
		return casBlackKingPos;
	}
	
	/**
	 * Methode die zurückgibt, ob die große Rochade möglich ist-
	 * @return true wenn true und fals wenn nich
	 */
	public boolean getCasQueenside() {
		if (isSideWhite)
		return casWhiteQueenPos;
		return casBlackQueenPos;
	}
	
	/**
	 * Methode die zurückgibt, welche Farbe in der momentanen Stellung am Zug ist.
	 * @return die Farbe als boolean (true bei Weiß)
	 */
	public boolean isSideWhite() {
		return isSideWhite;
	}
	
	/**
	 * Methode die zurückgibt, auf welchem Feld EnPassant möglich ist.
	 * @return das Feld in Feldwert
	 */
	public int getEnPassant() {
		return enPassant;
	}
	
	/**
	 * Methode um den letzten gezogenen Zug (der, der zu der aktuellen stellung geführt hat) in algebraischer Notation abzurufen.
	 * @return den Zug (z.B: e7e9)
	 */
	 public String getCurrMove() {
		 return currentMove;
	 }
	
	//ToString
	public String toString() {
		//Experimentelles Board
		StringBuffer returnString = new StringBuffer(200);
		for(int i = 0; i < 120 ; i++){
			returnString.append(board[i].toString());
			if(i%10 == 9)
				returnString.append("\n");
			else
				returnString.append(",");
		}
		if(currentMove != null)
			returnString.append("\n Current Move: '").append(currentMove).append("'");
		returnString.append("\n EP Feld: '").append(enPassant).append("'");
		returnString.append("\n cas q: " ).append(casBlackQueenPos);
		returnString.append("\n cas k: " ).append(casBlackKingPos);
		returnString.append("\n cas Q: " ).append(casWhiteQueenPos);
		returnString.append("\n cas K: " ).append(casWhiteKingPos);
		return (returnString.toString());
	}
	
	/**
	 * Methode die das Board zuruckgibt (ähnlich der ToString, allerdings mit einem übergebbaren String vor jeder Zeile.
	 * @param indent der String, der vor jeder Zeile angezeigt werden soll
	 * @throws IOException wenn was mit dem Writer nicht stimmt (da kann die Methode dann nix für).
	 */
	public void print(String indent, BufferedWriter out) throws IOException {
		StringBuffer returnString = new StringBuffer(indent);
		for (int i = 0; i < 120; i++) {
			returnString.append(board[i].toString());
			if (i % 10 == 9)
				returnString.append("\n" + indent);
			else
				returnString.append(",");
		}
		if(currentMove != null)
			returnString.append("\n" + indent + " Current Move: '").append(currentMove).append("'\n");
		returnString.append("\n" + indent + " EP Feld: '").append(enPassant).append("'");
		returnString.append("\n" + indent + " cas q: " ).append(casBlackQueenPos);
		returnString.append("\n" + indent + " cas k: " ).append(casBlackKingPos);
		returnString.append("\n" + indent + " cas Q: " ).append(casWhiteQueenPos);
		returnString.append("\n" + indent + " cas K: " ).append(casWhiteKingPos);
		returnString.append("\n");
		out.write(returnString.toString());
		
	}
}
